/* This example code is in the Public Domain (or CC0 licensed, at your option.)
   Unless required by applicable law or agreed to in writing, this software is
   distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"

#include "esp_hidd_prf_api.h"
#include "esp_bt_defs.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_gatt_defs.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "driver/gpio.h"
#include "hid_dev.h"

/**
 * Brief:
 * This example Implemented BLE HID device profile related functions, in which the HID device
 * has 4 Reports (1 is mouse, 2 is keyboard and LED, 3 is Consumer Devices, 4 is Vendor devices).
 * Users can choose different reports according to their own application scenarios.
 * BLE HID profile inheritance and USB HID class.
 */

/**
 * Note:
 * 1. Win10 does not support vendor report , So SUPPORT_REPORT_VENDOR is always set to FALSE, it defines in hidd_le_prf_int.h
 * 2. Update connection parameters are not allowed during iPhone HID encryption, slave turns
 * off the ability to automatically update connection parameters during encryption.
 * 3. After our HID device is connected, the iPhones write 1 to the Report Characteristic Configuration Descriptor,
 * even if the HID encryption is not completed. This should actually be written 1 after the HID encryption is completed.
 * we modify the permissions of the Report Characteristic Configuration Descriptor to `ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE_ENCRYPTED`.
 * if you got `GATT_INSUF_ENCRYPTION` error, please ignore.
 */

#define HID_DEMO_TAG "HID_DEMO"
#define GPIO_OUTPUT_IO_XM    18
#define GPIO_OUTPUT_IO_XP    19
#define GPIO_OUTPUT_IO_YM    6
#define GPIO_OUTPUT_IO_YP    7
#define GPIO_OUTPUT_PIN_SEL  ((1ULL<<GPIO_OUTPUT_IO_XM) | (1ULL<<GPIO_OUTPUT_IO_XP) | (1ULL<<GPIO_OUTPUT_IO_YM) | (1ULL<<GPIO_OUTPUT_IO_YP))
#define PBout(n,v) gpio_set_level(n, v)
#define  XM(v) PBout(GPIO_OUTPUT_IO_XM,v)
#define  YM(v) PBout(GPIO_OUTPUT_IO_YM,v)
#define  XP(v) PBout(GPIO_OUTPUT_IO_XP,v)
#define  YP(v) PBout(GPIO_OUTPUT_IO_YP,v)
static uint16_t hid_conn_id = 0;
static bool sec_conn = false;
static bool send_volum_up = false;
#define CHAR_DECLARATION_SIZE   (sizeof(uint8_t))

static void hidd_event_callback(esp_hidd_cb_event_t event, esp_hidd_cb_param_t *param);

#define HIDD_DEVICE_NAME            "HID"
void adc_main(void *pvParameters);
void gpio_init(void)
{
    //zero-initialize the config structure.
    gpio_config_t io_conf = {};
    //disable interrupt
    io_conf.intr_type = GPIO_INTR_DISABLE;
    //set as output mode
    io_conf.mode = GPIO_MODE_OUTPUT;
    //bit mask of the pins that you want to set,e.g.GPIO18/19
    io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
    //disable pull-down mode
    io_conf.pull_down_en = 0;
    //disable pull-up mode
    io_conf.pull_up_en = 0;
    //configure GPIO with the given settings
    gpio_config(&io_conf);
}

static uint8_t hidd_service_uuid128[] = {
    /* LSB <--------------------------------------------------------------------------------> MSB */
    //first uuid, 16bit, [12],[13] is the value
    0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x12, 0x18, 0x00, 0x00,
};

static esp_ble_adv_data_t hidd_adv_data = {
    .set_scan_rsp = false,
    .include_name = true,
    .include_txpower = true,
    .min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec
    .max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec
    .appearance = 0x03c0,       //HID Generic,
    .manufacturer_len = 0,
    .p_manufacturer_data =  NULL,
    .service_data_len = 0,
    .p_service_data = NULL,
    .service_uuid_len = sizeof(hidd_service_uuid128),
    .p_service_uuid = hidd_service_uuid128,
    .flag = 0x6,
};

static esp_ble_adv_params_t hidd_adv_params = {
    .adv_int_min        = 0x20,
    .adv_int_max        = 0x30,
    .adv_type           = ADV_TYPE_IND,
    .own_addr_type      = BLE_ADDR_TYPE_PUBLIC,
    //.peer_addr            =
    //.peer_addr_type       =
    .channel_map        = ADV_CHNL_ALL,
    .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};


static void hidd_event_callback(esp_hidd_cb_event_t event, esp_hidd_cb_param_t *param)
{
    switch(event) {
        case ESP_HIDD_EVENT_REG_FINISH: {
            if (param->init_finish.state == ESP_HIDD_INIT_OK) {
                //esp_bd_addr_t rand_addr = {0x04,0x11,0x11,0x11,0x11,0x05};
                esp_ble_gap_set_device_name(HIDD_DEVICE_NAME);
                esp_ble_gap_config_adv_data(&hidd_adv_data);

            }
            break;
        }
        case ESP_BAT_EVENT_REG: {
            break;
        }
        case ESP_HIDD_EVENT_DEINIT_FINISH:
	     break;
		case ESP_HIDD_EVENT_BLE_CONNECT: {
            ESP_LOGI(HID_DEMO_TAG, "ESP_HIDD_EVENT_BLE_CONNECT");
            hid_conn_id = param->connect.conn_id;
            break;
        }
        case ESP_HIDD_EVENT_BLE_DISCONNECT: {
            sec_conn = false;
            ESP_LOGI(HID_DEMO_TAG, "ESP_HIDD_EVENT_BLE_DISCONNECT");
            esp_ble_gap_start_advertising(&hidd_adv_params);
            break;
        }
        case ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT: {
            ESP_LOGI(HID_DEMO_TAG, "%s, ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT", __func__);
            ESP_LOG_BUFFER_HEX(HID_DEMO_TAG, param->vendor_write.data, param->vendor_write.length);
        }
        default:
            break;
    }
    return;
}

static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
    switch (event) {
    case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
        esp_ble_gap_start_advertising(&hidd_adv_params);
        break;
     case ESP_GAP_BLE_SEC_REQ_EVT:
        for(int i = 0; i < ESP_BD_ADDR_LEN; i++) {
             ESP_LOGD(HID_DEMO_TAG, "%x:",param->ble_security.ble_req.bd_addr[i]);
        }
        esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
	 break;
     case ESP_GAP_BLE_AUTH_CMPL_EVT:
        sec_conn = true;
        esp_bd_addr_t bd_addr;
        memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
        ESP_LOGI(HID_DEMO_TAG, "remote BD_ADDR: %08x%04x",\
                (bd_addr[0] << 24) + (bd_addr[1] << 16) + (bd_addr[2] << 8) + bd_addr[3],
                (bd_addr[4] << 8) + bd_addr[5]);
        ESP_LOGI(HID_DEMO_TAG, "address type = %d", param->ble_security.auth_cmpl.addr_type);
        ESP_LOGI(HID_DEMO_TAG, "pair status = %s",param->ble_security.auth_cmpl.success ? "success" : "fail");
        if(!param->ble_security.auth_cmpl.success) {
            ESP_LOGE(HID_DEMO_TAG, "fail reason = 0x%x",param->ble_security.auth_cmpl.fail_reason);
        }
        break;
    default:
        break;
    }
}
extern int xpos;
extern int ypos;
void adc_get(int axis);
void adc_init();
void hid_demo_task(void *pvParameters) //任务函数
{
    while (!sec_conn) {}
    vTaskDelay(2000 / portTICK_PERIOD_MS);
    int counter=0;
    int xprev=0;
    int yprev=0;
    const int DELTA = 20;
    bool isTouched =false;
 
    int float_xpos=2550;
    int float_ypos=3220;
    int not_stable_counter=0;
    int is_stable_counter=0;
    int still_counter=0;
    for(int i=0;i<1000;i++)
    {
        YM(0); //Y方向PNP管关
        YP(1); //Y方向NPN管关
        XM(1); //X方向PNP管开  
        XP(0); //X方向NPN管开
        //vTaskDelay(20 / portTICK_PERIOD_MS);
        adc_get(1); //检测触摸点的Y坐标

        //Y方向施加电压，X电极检测
        YM(1);
        YP(0);
        XM(0);  
        XP(1); 
        //vTaskDelay(20 / portTICK_PERIOD_MS);
        adc_get(0); //检测触摸点的X坐标
        if(abs(xpos-float_xpos)>DELTA || abs(ypos-float_ypos)>DELTA)
            not_stable_counter++;
        else
            is_stable_counter++;
        if(not_stable_counter>10)
            return;
        if(is_stable_counter>20)
            break;           
        if(i%10==0) //由于速度太快，来不及观察，因此大约每秒显示一次检测结果，仅用于调试时观测
            ESP_LOGI(HID_DEMO_TAG, "stable_counter: %d:%d xpos: %d: ypos: %d ",is_stable_counter,not_stable_counter,xpos,ypos);
        vTaskDelay(20 / portTICK_PERIOD_MS);
    }
    xprev=xpos;
    yprev=ypos;
    int float_counter=0;
    while(1) { //无限循环

        //注意触摸屏驱动电路中控制3.3V开关的三极管是NPN型（基极电压低时三极管导通），控制0V的三极管是PNP型（基极电压高时三极管导通），思考一下原因。
        //X方向施加电压；Y方向不加电，作为采样电极，检测触摸点电压。
        YM(0); //Y方向PNP管关
        YP(1); //Y方向NPN管关
        XM(1); //X方向PNP管开  
        XP(0); //X方向NPN管开
        //vTaskDelay(20 / portTICK_PERIOD_MS);
        adc_get(1); //检测触摸点的Y坐标
        
        //Y方向施加电压，X电极检测
        YM(1);
        YP(0);
        XM(0);  
        XP(1); 
        //vTaskDelay(20 / portTICK_PERIOD_MS);
        adc_get(0); //检测触摸点的X坐标
        //延时10毫秒
        if (sec_conn) { //如果蓝牙已连接
            uint8_t mouse_button = 0;//{HID_MOUSE_LEFT，HID_MOUSE_RIGHT，HID_MOUSE_MIDDLE}; 0表示仅移动鼠标
            //此处需补充将触摸屏检测到的绝对坐标转化为鼠标的相对坐标
            int8_t mickeys_x = 0;//(xpos - 1984) / 1000;
            int8_t mickeys_y = 0;//(ypos - 1967) / 1000;

            if(abs(xpos-float_xpos)<DELTA && abs(ypos-float_ypos)<DELTA){    //无触摸 
                float_counter++;
                if(float_counter>10)
                    isTouched=false;   
            }
            else if(0){//!isTouched){ //首次触摸
                isTouched=true;  
                float_counter=0;
                
                mickeys_x=xpos*1920/2500;
                mickeys_y=ypos*1080/3000;
                int is=0;
                for(is=0;is<1920/127;is++)
                {
                    esp_hidd_send_mouse_value(hid_conn_id, mouse_button, -127, -127);
                    vTaskDelay(20 / portTICK_PERIOD_MS);
                }
                    
                int x_step=mickeys_x/127;
                int y_step=mickeys_y/127;
                for(int is=0;is<x_step;is++)
                {
                    esp_hidd_send_mouse_value(hid_conn_id, mouse_button, 127, 0);//鼠标移到触摸点
                    vTaskDelay(20 / portTICK_PERIOD_MS);
                }
                esp_hidd_send_mouse_value(hid_conn_id, mouse_button, mickeys_x%127, 0);
                vTaskDelay(20 / portTICK_PERIOD_MS);
                for(int is=0;is<y_step;is++)
                {
                    esp_hidd_send_mouse_value(hid_conn_id, mouse_button, 0,127);//鼠标移到触摸点
                    vTaskDelay(20 / portTICK_PERIOD_MS);
                }
                esp_hidd_send_mouse_value(hid_conn_id, mouse_button, 0,mickeys_y%127);
                vTaskDelay(20 / portTICK_PERIOD_MS);

            } 
            else{  //持续触摸
                if(xpos<0 || ypos<0)
                    continue;
                mickeys_x=(xpos-xprev)*1920/2500;
                mickeys_y=(ypos-yprev)*1080/3000;
                if(abs(mickeys_x)>50 || abs(mickeys_y) >50)
                {
                    xprev=xpos;
                    yprev=ypos;
                    vTaskDelay(25/ portTICK_PERIOD_MS);  
                    continue;
                }
                if(abs(mickeys_x)<10 && abs(mickeys_y)<10)
                {
                    still_counter++;
                }
                else
                {
                    mouse_button=0;
                    still_counter=0;
                }
                if(still_counter==30)
                {
                    //float_xpos=xpos;
                    //float_ypos=ypos;
                    mouse_button=249;//HID_MOUSE_LEFT_DOWN;
                    ESP_LOGI(HID_DEMO_TAG,"Click");
                    esp_hidd_send_mouse_value(hid_conn_id, mouse_button, 0, 0);//鼠标移动
                    mouse_button=248;//HID_MOUSE_UP;
                    esp_hidd_send_mouse_value(hid_conn_id, mouse_button, 0, 0);//鼠标移动

                    //esp_hidd_send_mouse_value(hid_conn_id, 0, 0, 0);//鼠标移动
                    //vTaskDelay(500/ portTICK_PERIOD_MS);
                    //esp_hidd_send_mouse_value(hid_conn_id, mouse_button, 0, 0);//鼠标移动
                    //esp_hidd_send_mouse_value(hid_conn_id, 0, 0, 0);//鼠标移动
                    //still_counter=0;
                }
                else if(still_counter==32)
                {
                    mouse_button=HID_MOUSE_LEFT;
                    ESP_LOGI(HID_DEMO_TAG,"DoubleClick");
                    mouse_button=249;//HID_MOUSE_LEFT_DOWN;
                    esp_hidd_send_mouse_value(hid_conn_id, mouse_button, 0, 0);//鼠标移动
                    mouse_button=248;//HID_MOUSE_UP;
                    esp_hidd_send_mouse_value(hid_conn_id, mouse_button, 0, 0);//鼠标移动
                    still_counter=0;
                }
                else if(still_counter<30)
                    esp_hidd_send_mouse_value(hid_conn_id, mouse_button, mickeys_x, -mickeys_y);//鼠标移动
                float_counter=0;
                
            }
            vTaskDelay(25/ portTICK_PERIOD_MS);


            /*if(counter<10000){
                counter++;
                if(counter%2==0) //由于速度太快，来不及观察，因此大约每秒显示一次检测结果，仅用于调试时观测
                    ESP_LOGI(HID_DEMO_TAG, "Touched: %d xpos: %d:%d, ypos: %d:%d mickeys: (%d,%d) ",isTouched?1:0,xpos,xprev,ypos,yprev,mickeys_x,mickeys_y);
            }
            else
                counter=0;*/

        }
        xprev=xpos;
        yprev=ypos;  
    }
}

void app_main(void) //主程序入口
{
    esp_err_t ret;

    // Initialize NVS.（flash存储初始化，通常用于保存蓝牙、wifi等配置信息，不会因为掉电而丢失）
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK( ret );
    //蓝牙设备的初始化、连接主机等
    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));

    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    ret = esp_bt_controller_init(&bt_cfg);
    if (ret) {
        ESP_LOGE(HID_DEMO_TAG, "%s initialize controller failed\n", __func__);
        return;
    }

    ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
    if (ret) {
        ESP_LOGE(HID_DEMO_TAG, "%s enable controller failed\n", __func__);
        return;
    }

    ret = esp_bluedroid_init();
    if (ret) {
        ESP_LOGE(HID_DEMO_TAG, "%s init bluedroid failed\n", __func__);
        return;
    }

    ret = esp_bluedroid_enable();
    if (ret) {
        ESP_LOGE(HID_DEMO_TAG, "%s init bluedroid failed\n", __func__);
        return;
    }

    if((ret = esp_hidd_profile_init()) != ESP_OK) {
        ESP_LOGE(HID_DEMO_TAG, "%s init bluedroid failed\n", __func__);
    }

    ///register the callback function to the gap module
    esp_ble_gap_register_callback(gap_event_handler);
    esp_hidd_register_callbacks(hidd_event_callback);

    /* set the security iocap & auth_req & key size & init key response key parameters to the stack*/
    esp_ble_auth_req_t auth_req = ESP_LE_AUTH_BOND;     //bonding with peer device after authentication
    esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;           //set the IO capability to No output No input
    uint8_t key_size = 16;      //the key size should be 7~16 bytes
    uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
    uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
    esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
    esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
    esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
    /* If your BLE device act as a Slave, the init_key means you hope which types of key of the master should distribute to you,
    and the response key means which key you can distribute to the Master;
    If your BLE device act as a master, the response key means you hope which types of key of the slave should distribute to you,
    and the init key means which key you can distribute to the slave. */
    esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t));
    esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t));
    adc_init();//adc（analog to digital conversion模数转化）采样硬件初始化（GPIO2、GPIO3）
    gpio_init();//GPIO是General Purpose Input Output的简称,简单叫做通用型输入输出，此处是用于触摸屏电压控制的GPIO端口（GPIO6、GPIO7、GPIO18、GPIO19）硬件初始化
    xTaskCreate(&hid_demo_task, "hid_task", 2048, NULL, 5, NULL); //开始后台任务，hid_demo_task是任务对应的具体函数，在上文中已定义

}
